#pragma once

/*
 * CMultilineList custom control
 * Copyright (C) 2006 Dave Calkins (coder1024@gmail.com)
 *
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2.1 of the License, or (at your option)
 * any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307 USA 
 *
 */

//******************************************************************************
// REVISION HISTORY
//------------------------------------------------------------------------------
// 2006.09.04 - initial release
//******************************************************************************

// default column width in pixels; this is used if width for a column is
// not specified
#define DEFAULT_COLUMN_WIDTH 100

/**
 * A custom control which provides a multi-column list with varying row heights,
 * allowing multi-line text in the cells.  Cell text is wrapped as necessary and
 * may contain '\n' chars which will force line breaks.
 *
 * To use, set the overall size (# cols, # rows), the column headings, and widths
 * and then set individual cell text.
 *
 * When the current row selection changes, this control will send a WM_COMMAND
 * message to its parent with the LBN_SELCHANGE code.  i.e., it notifies its parent
 * of selection changes using the same technique used by the standard list box.
 *
 * There are 2 ways to use this control in your code.
 *
 * 1) You can create the control dynamically by adding an instance of this class
 *    to your parent window class and then calling Create() with the desired
 *    geometry
 * 2) You can also use the VS dialog editor to allow you to visually place the
 *    control.  To do this, create a custom control, position and size as
 *    desired, and then set the Class property to "CMultilineList" (without the
 *    double-quotes).  You can then right-click on the control and select
 *    "Add Variable" and then use that added variable in your code.
 */

#define	WM_LIST_BUTTON_CLICKED	(WM_USER+0x100)
typedef struct tagNMMultilineList{
	NMHDR   hdr;
	intptr_t     row;
	intptr_t     col;
} NMMultilineList, FAR *LPNMMultilineList;

/// holds information about a single column
class Column
{
public:

	// column width in pixels
	intptr_t m_width;
	UINT m_format;

	// heading text to display above the column in the header control
	CString m_heading;

	Column()
	{
		m_width = DEFAULT_COLUMN_WIDTH; m_format = DT_LEFT | DT_NOPREFIX | DT_TOP | DT_WORDBREAK;
		m_heading = _T(" ");
	}
};

class CCellButton :public CButton
{
	DECLARE_DYNAMIC(CCellButton)
public:
	intptr_t col, row;
	CCellButton(intptr_t c, intptr_t r) :col(c), row(r) {};
};

class CMultilineList;
class CMultilineContent : public CWnd
{
	DECLARE_DYNAMIC(CMultilineContent)
public:

	CMultilineContent();
	~CMultilineContent() override;

	BOOL Create(CMultilineList* pParentWnd, const RECT& rect, UINT nID);
	BOOL DestroyWindow() override;

	/**
	* Sets the overall geometry of the grid.  When reducing the size using this
	* method, anything which is then outside the new size is deleted.  You must call
	* this before anything else to ensure the size you need is available.
	* @param nCols Desired number of columns (which are numbered [0...N-1])
	* @param nRows Desired number of rows (which are numbered [0...N-1])
	*/
	bool SetSize(intptr_t nCols, intptr_t nRows);
	/**
	* Gets the current overall geometry of the grid.
	* @param nCols will be initialized with the number of columns (which are numbered [0...N-1])
	* @param nRows will be initialized with the number of rows (which are numbered [0...N-1])
	*/
	void GetSize(intptr_t & nCols, intptr_t & nRows);
	/**
	* Sets the text shown in the heading above the specified column.
	* @param col the column number [0...N-1]
	* @param heading the text to be displayed
	*/
	bool SetColHeading(intptr_t col, LPCTSTR heading);
	/**
	* Sets the width of the specified column in pixels.  Note that the user cal
	* also change this themselves by dragging the column separators in the
	* header control.
	* @param col the column number [0...N-1]
	* @param width desired width of the column in pixels
	*/
	bool SetColWidth(intptr_t col, intptr_t width, UINT format = DT_LEFT | DT_NOPREFIX | DT_TOP | DT_WORDBREAK);
	/**
	* Sets the text for the specified cell.  The text may contain '\n' which
	* will cause line breaks and heightening of the row as needed.  The text
	* will also be word-wrapped to ensure it fits within the column width.
	* @param col the column number [0...N-1]
	* @param row the row number [0...N-1]
	* @param text the desired text for the cell
	*/
	bool SetCellText(intptr_t col, intptr_t row, LPCTSTR text);
	/**
	* Gets the current text in the specified cell.
	* @param col the column number [0...N-1]
	* @param row the row number [0...N-1]
	* @return text in the specified cell
	*/
	CString GetCellText(intptr_t col, intptr_t row);
	/**
	* changes the current selection to the specified row.  changing the selection
	* programmatically using this method will not trigger a WM_COMMAND notification
	* message to be sent to the parent window.
	* @param row the row number to select [0...N-1]
	*/
	bool SetSelRow(intptr_t row);
	/**
	* Gets the row number [0...N-1] of the currently selected row.
	* @return the currently selected row number [0...N-1] or -1 if no row is currently selected
	*/
	intptr_t GetSelRow();
	/**
	* Gets the row which is displayed at the specified client co-ords in the window
	* @param pt the point in client coords
	* @return the row number [0...N-1] which the specified point is over (or -1 if there is no row at that point)
	*/
	intptr_t GetRowFromPoint(CPoint pt);
	/**
	* Ensure the specified row is visible
	* @param row the row number [0...N-1]
	*/
	bool EnsureRowIsVisible(intptr_t row, intptr_t &viewYPos);
	bool SetCellButton(intptr_t col, intptr_t row, LPCTSTR caption);
	bool ClearCellButton(intptr_t col, intptr_t row);
	bool ColHeadTrack(NMHDR * pNMHDR);
	intptr_t getTotalWidth();
	intptr_t getTotalHeight();
	void UpdateButtons(intptr_t viewXPos, intptr_t viewYPos);
	bool VScroll(UINT nSBCode, UINT nPos, intptr_t &viewYPos);
	bool HScroll(UINT nSBCode, UINT nPos, intptr_t &viewXPos);

protected:
	CMultilineList *m_pParent;
	BOOL OnCommand(WPARAM wParam, LPARAM lParam) override;
public:
	/// called to erase the background
	afx_msg BOOL OnEraseBkgnd(CDC* pDC);
	/// called to paint the contents of the window
	afx_msg void OnPaint();
	/// called when user clicks in the window
	afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
	/// called in response to mouse-wheel
	afx_msg BOOL OnMouseWheel(UINT nFlags, short zDelta, CPoint pt);

	// the below provide an offscreen rendering surface, allowing us to render
	// to that surface and then blt the resultant image into the window's DC
	CDC     m_offscreenDC;
	CBitmap m_offscreenBitmap;
	CSize   m_offscreenBitmapSize;
	CPen	m_gridPen;
	CFont	m_defaultFont;
	// the overall size of the list (# of columns and # of rows)
	intptr_t m_nCols, m_nRows;
	// currently selected row
	intptr_t m_curSelRow;

	LPCWSTR *m_cells;	//row*col
	CCellButton **m_buttons; intptr_t m_buttonCount;
	intptr_t *m_rowHeights;	//row
	Column **m_columns;	//col


	/// ensures the offscreen drawing surface is available and sized to the window's client area
	BOOL PrepareOffscreenSurface();
	/// ensures a complete set of valid row heights is available
	void CalculateRowHeights();
	/// renders the content area (client area minus the border, header control, and scroll bar)
	void RenderContent(CDC & dc, CRect & r, intptr_t viewXPos, intptr_t viewYPos);
	Column *getColumn(intptr_t col, bool newForNull);
	CCellButton *getCellButton(intptr_t col, intptr_t row);
	void removeCellButton(CCellButton *pButton);

	DECLARE_MESSAGE_MAP()
protected:
	// width of the internal borders used between cells/cols/rows
	int GRID_WIDTH;
	// spacing inside cells between the text and the cell borders
	int INNER_PADDING;
	int BUTTON_HEIGHT;
	// # pixels to scroll horizontally when using arrows
	int HORZSCROLL_PIXELS;
};



class CMultilineList : public CWnd
{
	DECLARE_DYNAMIC(CMultilineList)
public:
	CMultilineList();
	~CMultilineList() override;

	/**
	* You can use this method to dynamically create an instance of the control.
	*/
	BOOL Create(CWnd* pParentWnd, const RECT& rect, UINT nID, DWORD dwStyle = WS_VISIBLE);
	BOOL DestroyWindow() override;

	void SetSize(intptr_t nCols, intptr_t nRows);
	void GetSize(intptr_t & nCols, intptr_t & nRows){ m_content.GetSize(nCols, nRows); }
	void SetColHeading(intptr_t col, LPCTSTR heading);
	void SetColWidth(intptr_t col, intptr_t width, UINT format = DT_LEFT | DT_NOPREFIX | DT_TOP | DT_WORDBREAK);
	void SetCellText(intptr_t col, intptr_t row, LPCTSTR text);
	CString GetCellText(intptr_t col, intptr_t row){ return m_content.GetCellText(col, row); };
	void SetSelRow(intptr_t row, bool ensureVisible = true, bool notify = true);
	intptr_t GetSelRow(){ return m_content.GetSelRow(); };
	intptr_t GetRowFromPoint(CPoint pt);
	void EnsureRowIsVisible(intptr_t row);
	void SetCellButton(intptr_t col, intptr_t row, LPCTSTR caption);
	void ClearCellButton(intptr_t col, intptr_t row);

public: // windows message handlers
	afx_msg BOOL OnEraseBkgnd(CDC* pDC);
	/// called when the window has been resized
	afx_msg void OnSize(UINT nType, int cx, int cy);

	/// called when scrolling occurs using the vertical scrollbar
	afx_msg void OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar);

	/// called when scrolling occurs using the horizontal scrollbar
	afx_msg void OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar);

	// tracks the position of the top left of the visible area (# pixels relative to top and left of the list)
	intptr_t m_viewYPos;
	intptr_t m_viewXPos;

protected:

   /// called at initialization
   void PreSubclassWindow() override;

   /// called when receiving notification messages from the child controls
   BOOL OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult) override;

   /// registers the window class
   BOOL RegisterWindowClass();

   /**
    * child header control, used to display the column headings and to allow
    * dynamic resize of columns by the user
    */
   CHeaderCtrl m_headerCtrl;
   CMultilineContent m_content;
   /**
    * child vertical scrollbar control
    */
   CScrollBar  m_scrollBar;

   /**
    * child horizontal scrollbar control
    */
   CScrollBar  m_scrollBarHorz;

   // misc GDI objects used when rendering
   CFont m_headerFont;

   /// creates the child controls during initialization
   void CreateChildControls();

   /// positions/sizes the child controls based on the current window size
   void PositionChildControls();

   /// updates the state of the child controls to match the state of this control
   void UpdateChildControls();

   /// calcuates the content area rect in window client coords
   void SetContentRect();

   DECLARE_MESSAGE_MAP()
protected:
	// header control height is set to be the font height plus the below
	int HEADERCTRL_HEIGHT_EXTRA;
};


